{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-25T01:49:52.474036500Z",
     "start_time": "2024-04-25T01:49:45.800288200Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=9, micro=7, releaselevel='final', serial=0)\n",
      "matplotlib 3.8.4\n",
      "numpy 1.26.4\n",
      "pandas 2.2.2\n",
      "sklearn 1.4.2\n",
      "torch 2.2.2+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-25T01:50:02.979234500Z",
     "start_time": "2024-04-25T01:49:59.968553300Z"
    }
   },
   "outputs": [],
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# fashion_mnist图像分类数据集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 当然也可以用 torch.utils.data.Dataset 实现人为划分\n",
    "# 从数据集到dataloader\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=16, shuffle=True)\n",
    "val_loader = torch.utils.data.DataLoader(test_ds, batch_size=16, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-25T01:52:08.680444900Z",
     "start_time": "2024-04-25T01:51:54.622208Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.2860]), tensor([0.3205]))\n"
     ]
    }
   ],
   "source": [
    "from torchvision.transforms import Normalize\n",
    "\n",
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "print(cal_mean_std(train_ds))\n",
    "# 0.2860， 0.3205\n",
    "transforms = nn.Sequential(\n",
    "    Normalize([0.2860], [0.3205])\n",
    ")\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型\n",
    "\n",
    "这里我们没有用`nn.Linear`的默认初始化，而是采用了xavier均匀分布去初始化全连接层的权重\n",
    "\n",
    "xavier初始化出自论文 《Understanding the difficulty of training deep feedforward neural networks》，适用于使用`tanh`和`sigmoid`激活函数的方法。当然，我们这里的模型采用的是`relu`激活函数，采用He初始化（何凯明初始化）会更加合适。感兴趣的同学可以自己动手修改并比对效果。\n",
    "\n",
    "|神经网络层数|初始化方式|early stop at epoch| val_loss | vla_acc|\n",
    "|-|-|-|-|-|\n",
    "|20|默认|\n",
    "|20|xaviier_uniform|\n",
    "|20|he_uniform|\n",
    "|...|\n",
    "\n",
    "He初始化出自论文 《Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification》"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:08:24.047238700Z",
     "start_time": "2023-11-19T07:08:23.988505400Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Linear_00\tparamerters num: 78400\n",
      "Linear_00\tparamerters num: 100\n",
      "Linear_01\tparamerters num: 10000\n",
      "Linear_01\tparamerters num: 100\n",
      "Linear_02\tparamerters num: 10000\n",
      "Linear_02\tparamerters num: 100\n",
      "Linear_03\tparamerters num: 10000\n",
      "Linear_03\tparamerters num: 100\n",
      "Linear_04\tparamerters num: 10000\n",
      "Linear_04\tparamerters num: 100\n",
      "Linear_05\tparamerters num: 10000\n",
      "Linear_05\tparamerters num: 100\n",
      "Linear_06\tparamerters num: 10000\n",
      "Linear_06\tparamerters num: 100\n",
      "Linear_07\tparamerters num: 10000\n",
      "Linear_07\tparamerters num: 100\n",
      "Linear_08\tparamerters num: 10000\n",
      "Linear_08\tparamerters num: 100\n",
      "Linear_09\tparamerters num: 10000\n",
      "Linear_09\tparamerters num: 100\n",
      "Linear_10\tparamerters num: 10000\n",
      "Linear_10\tparamerters num: 100\n",
      "Linear_11\tparamerters num: 10000\n",
      "Linear_11\tparamerters num: 100\n",
      "Linear_12\tparamerters num: 10000\n",
      "Linear_12\tparamerters num: 100\n",
      "Linear_13\tparamerters num: 10000\n",
      "Linear_13\tparamerters num: 100\n",
      "Linear_14\tparamerters num: 10000\n",
      "Linear_14\tparamerters num: 100\n",
      "Linear_15\tparamerters num: 10000\n",
      "Linear_15\tparamerters num: 100\n",
      "Linear_16\tparamerters num: 10000\n",
      "Linear_16\tparamerters num: 100\n",
      "Linear_17\tparamerters num: 10000\n",
      "Linear_17\tparamerters num: 100\n",
      "Linear_18\tparamerters num: 10000\n",
      "Linear_18\tparamerters num: 100\n",
      "Linear_19\tparamerters num: 10000\n",
      "Linear_19\tparamerters num: 100\n",
      "Linear_20\tparamerters num: 1000\n",
      "Linear_20\tparamerters num: 10\n"
     ]
    }
   ],
   "source": [
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, layers_num=2):\n",
    "        super().__init__()\n",
    "        self.transforms = transforms\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 多加几层\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(28 * 28, 100),  # in_features=784, out_features=300\n",
    "            nn.ReLU(),\n",
    "        )\n",
    "        # 加19层\n",
    "        for i in range(1, layers_num):\n",
    "            self.linear_relu_stack.add_module(f\"Linear_{i}\", nn.Linear(100, 100))\n",
    "            self.linear_relu_stack.add_module(f\"selu\", nn.SELU()) # 这里采用SELU激活函数\n",
    "        # 输出层\n",
    "        self.linear_relu_stack.add_module(\"Output Layer\", nn.Linear(100, 10))\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 1, 28, 28]\n",
    "        x = self.transforms(x)\n",
    "        x = self.flatten(x)  \n",
    "        # 展平后 x.shape [batch size, 28 * 28]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 10]\n",
    "        return logits\n",
    "\n",
    "for idx, (key, value) in enumerate(NeuralNetwork(20).named_parameters()):\n",
    "    print(f\"Linear_{idx // 2:>02}\\tparamerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:08:24.086783800Z",
     "start_time": "2023-11-19T07:08:24.006259100Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:08:29.680563100Z",
     "start_time": "2023-11-19T07:08:24.080712700Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From C:\\Program Files\\Python39\\lib\\site-packages\\keras\\src\\losses.py:2976: The name tf.losses.sparse_softmax_cross_entropy is deprecated. Please use tf.compat.v1.losses.sparse_softmax_cross_entropy instead.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:08:29.695770500Z",
     "start_time": "2023-11-19T07:08:29.683564700Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:08:29.715988200Z",
     "start_time": "2023-11-19T07:08:29.697770700Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:25:30.236190500Z",
     "start_time": "2023-11-19T07:08:29.715988200Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "  0%|          | 0/375000 [00:00<?, ?it/s]",
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "01cd1d0c4038417992857a7ad9033cab"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 41 / global_step 153750\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                    \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork(layers_num=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "tensorboard_callback = TensorBoardCallback(\"runs/selu\")\n",
    "tensorboard_callback.draw_model(model, [1, 28, 28])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints/selu\"):\n",
    "    #创建多级目录\n",
    "    os.makedirs(\"checkpoints/selu\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints/selu\", save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.001)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:25:30.627983300Z",
     "start_time": "2023-11-19T07:25:30.252182600Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 864x360 with 2 Axes>",
      "image/png": "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\n"
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(6 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10000)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "## selu激活函数，可以加速收敛，效果相对于relu更好"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-19T07:25:32.093566900Z",
     "start_time": "2023-11-19T07:25:30.627983300Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3867\n",
      "accuracy: 0.8841\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/selu/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
